//*********************************************************************
//		Copyright (c) 2003 - 2008 Microsoft Corporation.
//      This code is provided under Microsoft Public License, which can be found at
//      http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx 
//
// @File: Collector.cs
//
// Purpose: Base Collector provides common functions for specialized collectors
//
//
//
// Notes: 
//
//
//	
// History: 
//     @Version: V2.1
//
//     07/14/08 PGV 2.1.001.0 Starting V2.1 (based on 2.0.030.0 code)
//     12/19/08 PGV 2.1.003.0 Fix for history generation
//
//
// @EndHeader@
//*********************************************************************

using System;
using System.Data;
using System.Data.SqlClient;
using System.Management;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Win32;
using System.Diagnostics;
using System.Text;

using H2V2;


namespace H2V2.Collector
{
    public class RegistryBaseKey
    {
        public string Name;
        public string Key;
        public string Node;
    }

    public struct RunDescriptor
    {
        public int srv_id;
        public short inst_id;
        public int run_id;

        public RunDescriptor (int a_srv_id, short a_inst_id, int a_run_id)
        {
            srv_id = a_srv_id;
            inst_id = a_inst_id;
            run_id = a_run_id;
        }
    }

    public class CollectorBase : StatusObject 
	{
		public static DateTime MIN_DATE = new DateTime (1990,1,1);
		public const uint MAX_ERRORS = 3;

		protected uint m_options = 0x7fffffff;
		protected int m_track_id = 0;
		protected int m_track_base = 0;

		public string m_inst_name;
		public string m_machine_name;
		public RunDescriptor m_rd;

		public uint Options { get {return m_options;} set { m_options = value; } }
		public virtual string Version { get { return "2.2.001.0"; } }
		public virtual string Name { get { return "base collector"; } }

		protected int m_collectorID = 0;
		public int CollectorID { get { return m_collectorID; } }

		protected Connection m_repository;

		protected RegistryKey m_hklm;

		// Unit members
		protected int m_CurrentUnit = 0;
		protected ArrayList m_UnitCommands_array;
        protected List<SqlCommand> m_UnitCommands;
		protected string m_hparam;

        public CollectorBase(string a_machine_name, string a_inst_name, RunDescriptor a_rd)
		{
			m_machine_name = a_machine_name;
			m_inst_name = a_inst_name;
			m_rd = a_rd;

			m_hparam = "@run_id="+m_rd.run_id+", @srv_id="+m_rd.srv_id;

			m_UnitCommands_array = new ArrayList ();
            m_UnitCommands = new List<SqlCommand> ();
		}

		public virtual void Init (Connection a_repository) 
		{
			ChangeStatus (" Initializing collector: "+Name);
			if (m_options != 0x7fffffff)
			{
				ChangeStatus ("Collector is running with non-default options 0x"+m_options.ToString ("X"), StatusEventType.WARNING);
			}

			m_repository = a_repository;
		}

		public virtual void Collect () {}

		public static string ConstructServiceName (string a_inst_name)
		{
			string name = (a_inst_name == "MSSQLSERVER")? "MSSQLSERVER" : "MSSQL$"+a_inst_name;
			return name;
		}

		public static string ConstructConnectionName (string a_machine_name, string a_inst_name)
		{
			string name = (a_inst_name == "MSSQLSERVER")? a_machine_name : a_machine_name+"\\"+a_inst_name;
			return name;
		}

        public static string FixSingleQuote (string a_str)
        {
            return a_str.Replace ("'", "''");
        }

		public static string GetInstanceKey (RegistryKey a_hklm, string a_inst_name, H2ProductLevel a_level)
		{
			string s_key = "";
            
			switch (a_level)
			{
				case H2ProductLevel.Sql2000SP3:
					if (a_inst_name == "MSSQLSERVER") 
						s_key = "SOFTWARE\\Microsoft\\MSSQLServer";
					else
						s_key = "SOFTWARE\\Microsoft\\Microsoft SQL Server\\"+a_inst_name;
					break;
				case H2ProductLevel.Sql2005:
                case H2ProductLevel.Sql2008:
					string ikey = null;
					RegistryKey mapkey;
					mapkey = a_hklm.OpenSubKey ("SOFTWARE\\Microsoft\\Microsoft SQL Server\\Instance Names\\SQL");
					if (mapkey == null)		
					{
						mapkey = a_hklm.OpenSubKey ("SOFTWARE\\Microsoft\\Microsoft SQL Server\\Instance Names");
					}
					if (mapkey != null)
					{
						ikey = (string)mapkey.GetValue (a_inst_name);
					}

                    if (ikey == null)
                    {
                        //attempt to get the key from 64bit values
                        ikey = null;
                        mapkey = a_hklm.OpenSubKey("SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SQL Server\\Instance Names\\SQL");
                        if (mapkey == null)
                        {
                            mapkey = a_hklm.OpenSubKey("SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SQL Server\\Instance Names");
                        }
                        if (mapkey != null)
                        {
                            ikey = (string)mapkey.GetValue(a_inst_name);
                        }

                        if (ikey == null)
                        {
                            throw new CollectorErrorException(CollectorError.CannotFindRegistryKey,
                                0, "Can't find instance-specific registry key");
                        }
                        s_key = "SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SQL Server\\" + ikey;
                    }
                    else
                    {
                        s_key = "SOFTWARE\\Microsoft\\Microsoft SQL Server\\" + ikey;
                    }
					break;
			}

			return s_key;
		}

		public static void CopySimpleDate (DateTime a_src, ref DateTime a_dest)
		{
			a_dest = new DateTime (a_src.Year, a_src.Month, a_src.Day, a_src.Hour, a_src.Minute, a_src.Second);
		}

		public void LogMgrError
            (RunErrorClass class_id, int obj_id, string obj_name, CollectorError error_id, int aux_error_id, string a_msg)
		{
            H2Manager.LogMgrError (m_rd.srv_id, m_rd.inst_id, class_id, obj_id, obj_name, error_id, aux_error_id, a_msg);
		}

		protected void CollectWmi (string a_scope, string a_query, uint a_type)
		{
			System.Management.ManagementScope ms = new System.Management.ManagementScope(a_scope); 

			System.Management.ObjectQuery oq = new System.Management.ObjectQuery(a_query);
			ManagementObjectSearcher query = new ManagementObjectSearcher(ms,oq);
			try 
			{
				ManagementObjectCollection queryCollection = query.Get();
				foreach (ManagementObject mo in queryCollection)
				{
					StoreWmi (a_type, mo);
				}
			}
			catch (ManagementException mngex)
			{
				ChangeStatus (mngex.StackTrace, StatusEventType.STACK_TRACE);
				ChangeStatus (a_query, StatusEventType.INFO);

				CollectorErrorException cerr;
				switch (mngex.ErrorCode)
				{
					case ManagementStatus.AccessDenied:
						ChangeStatus (mngex.Message, StatusEventType.ERROR);
						cerr = new CollectorErrorException (CollectorError.AccessDenied, mngex.ErrorCode, "Access denied: "+m_machine_name);
						break;
					case ManagementStatus.InvalidClass:
						ChangeStatus ("Invalid class", StatusEventType.ERROR);
                        cerr = new CollectorErrorException (CollectorError.WmiInvalidClass, mngex.ErrorCode, "Invalid class: " + a_query);
						break;
					case ManagementStatus.ProviderLoadFailure:
						ChangeStatus ("Provider Load Failure", StatusEventType.ERROR);
                        cerr = new CollectorErrorException (CollectorError.WmiProviderLoadFailure, mngex.ErrorCode, "Invalid class: " + a_query);
						break;
					default:
                        cerr = new CollectorErrorException (CollectorError.UnexpectedException, mngex.ErrorCode, mngex.ErrorCode.ToString ());
						break;
				}
				throw cerr;
			}
			catch (Exception ex)
			{
				ChangeStatus (ex.StackTrace, StatusEventType.STACK_TRACE);

				if (ex.Message.StartsWith ("A security package specific error occurred"))
                    throw new CollectorErrorException (CollectorError.SecurityError, 0, ex.Message);
				else 
					throw ex;
			}
		}

		protected void CollectWmiNoMsg (string a_scope, string a_query, uint a_type)
		{
			System.Management.ManagementScope ms = new System.Management.ManagementScope(a_scope); 

			System.Management.ObjectQuery oq = new System.Management.ObjectQuery(a_query);
			ManagementObjectSearcher query = new ManagementObjectSearcher(ms,oq);

			ManagementObjectCollection queryCollection = query.Get();
			foreach (ManagementObject mo in queryCollection)
			{
				StoreWmi (a_type, mo);
			}
		}

		protected virtual void StoreWmi (uint a_type, ManagementObject a_mo) {}
        
        protected void collect_Registry (RegistryKey a_rk, uint a_type, RegistryBaseKey a_rb, string a_key)
		{
			// This function recursevly travers Registry stopping at keys having values
			// It doesn't go deeper if a key with values has subkeys
			RegistryKey rk;

			if (a_rk.ValueCount <= 1) 
			{
				a_rb.Node = a_key;
				foreach (string key in a_rk.GetSubKeyNames ())
				{
					a_rb.Name = a_rk.Name;
					a_rb.Key = key;
					rk = a_rk.OpenSubKey (key);
					collect_Registry (rk, a_type, a_rb, key);
				}
			}
			else
			{
				StoreRegistry (a_type, a_rk, a_rb);
			}
		}

        protected virtual void StoreRegistry (uint a_type, RegistryKey a_key, RegistryBaseKey a_rb) {}

		protected int RegisterCollector ()
		{
			SqlCommand rcmd;
			SqlParameter p_name;
			SqlParameter p_version;
			SqlParameter p_c_id;
			SqlParameter retval;
			int retcode,
				c_id;


			rcmd = new SqlCommand ("dbo.spr_collector", m_repository.SQLConnection);
			rcmd.CommandType = CommandType.StoredProcedure;

			retval = new SqlParameter ("retvalue", SqlDbType.Int);
			retval.Direction = ParameterDirection.ReturnValue;
			rcmd.Parameters.Add (retval);
			p_name = new SqlParameter ("@name", SqlDbType.NVarChar);
			p_name.Direction = ParameterDirection.Input;
			p_name.Value = Name;
			rcmd.Parameters.Add (p_name);
			p_version = new SqlParameter ("@version", SqlDbType.NVarChar);
			p_version.Direction = ParameterDirection.Input;
			p_version.Value = Version;
			rcmd.Parameters.Add (p_version);
			p_c_id = new SqlParameter ("@c_id", SqlDbType.Int);
			p_c_id.Direction = ParameterDirection.Output;
			rcmd.Parameters.Add (p_c_id);

			rcmd.ExecuteNonQuery ();
			try 
			{
				retcode = (int)retval.Value;
				c_id = (int)p_c_id.Value;
			}
			catch (SqlException sqlex)
			{
				ChangeStatus (sqlex);
                throw (new StorageErrorException (CollectorError.StorSyntaxErrorReaderSP, sqlex.Number, "dbo.spr_collector"));
			}

			return c_id;
		}

		protected void CallHistoryProc (string proc)
		{
			UnitUpdateStatus (m_track_id);
			UnitAddData ("Exec "+proc+" "+m_hparam);
		}
 

		#region Unit interface
		// Units - logical pieces of collection. Unit stores all its data into DB as a single transaction.
		// Collection is spread in time. Data recording transaction should be as short as possible. 
		// Unit provides means to store data in memory, then store them in a single transaction.
		protected void UnitOpen (int a_number, string a_comment)
		{
			UnitCheck (0);
			ChangeStatus (" Opening Unit "+a_number+" ("+a_comment+")");
			m_CurrentUnit = a_number;

			if (m_UnitCommands_array.Count > 0)
			{
				m_UnitCommands_array.Clear ();
			}

			UnitRegister ();
		}

		protected void UnitClose ()
		{
			UnitCheck (1);
			ChangeStatus (" Closing Unit "+m_CurrentUnit);
			m_CurrentUnit = 0;

			m_UnitCommands_array.Clear ();
            m_UnitCommands.Clear ();
        }

		protected void UnitCheck (int a_dir)
		{
			if (a_dir == 1 && m_CurrentUnit == 0)
			{ // Unit is supposed to be open, but it is not
                throw new CollectorErrorException (CollectorError.MissingOpenUnit, 0, Name);
			}
			else if (a_dir == 0 && m_CurrentUnit != 0)
			{ // Unit is supposed to be closed, but it is not
                throw new CollectorErrorException (CollectorError.UnitAlreadyOpened, 0, Name);
			}

		}

		protected void UnitAddData (string a_str)
		{
			UnitCheck (1);
			m_UnitCommands_array.Add (a_str); 
		}

        protected void UnitAddData (SqlCommand cmd)
        {
            UnitCheck (1);
            m_UnitCommands.Add (cmd);
        }


		protected void UnitUpdateStatus (int a_state)
		{
			UnitCheck (1);

            SqlCommand cmd = new SqlCommand ();
            cmd.CommandText = "Update mgr_unit Set state=@state Where run_id=@run_id And c_id=@c_id And srv_id=@srv_id And inst_id=@inst_id And unit=@unit ";
            cmd.Parameters.AddWithValue ("@state", a_state);
            cmd.Parameters.AddWithValue ("@run_id", m_rd.run_id);
            cmd.Parameters.AddWithValue ("@c_id", m_collectorID);
            cmd.Parameters.AddWithValue ("@srv_id", m_rd.srv_id);
            cmd.Parameters.AddWithValue ("@inst_id", m_rd.inst_id);
            cmd.Parameters.AddWithValue ("@unit", m_CurrentUnit);

			m_repository.StoreNonQuery (cmd);
		}

		protected void UnitSaveData ()
		{
			int ret;

			UnitCheck (1);

            ChangeStatus("SaveData: " + m_UnitCommands.Count + " sql commands", StatusEventType.DEBUG);
            ChangeStatus("SaveData: " + m_UnitCommands_array.Count + " string commands", StatusEventType.DEBUG);

			m_repository.TransactionOpen ();

			try
			{
                foreach (SqlCommand cmd in m_UnitCommands)
                {
                    ret = m_repository.StoreNonQuery (cmd);
                }

				foreach (string cmd in m_UnitCommands_array)
				{
					ret = m_repository.StoreNonQuery (cmd);
				}

				m_repository.TransactionCommit ();
				UnitUpdateStatus (999);
			}
			catch 
			{
				m_repository.TransactionRollback ();
				throw;
			}
		}

		protected void UnitRegister ()
		{
            SqlCommand cmd = new SqlCommand ();
            cmd.CommandText = "Insert mgr_unit (run_id,c_id,srv_id,inst_id,unit,state) Values (@run_id,@c_id,@srv_id,@inst_id,@unit,@state)";
            cmd.Parameters.AddWithValue ("@run_id", m_rd.run_id);
            cmd.Parameters.AddWithValue ("@c_id", m_collectorID);
            cmd.Parameters.AddWithValue ("@srv_id", m_rd.srv_id);
            cmd.Parameters.AddWithValue ("@inst_id", m_rd.inst_id);
            cmd.Parameters.AddWithValue ("@unit", m_CurrentUnit);
            cmd.Parameters.AddWithValue ("@state", 0);

			m_repository.StoreNonQuery (cmd);
		}

		#endregion

	}
}
